package com.lipaluma.parser;

import java.io.Closeable;
import java.util.Iterator;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lipaluma.context.Context;
import com.lipaluma.exceptions.CsvParserException;
import com.lipaluma.exceptions.CsvParserValidationException;
import com.lipaluma.field.mapper.FieldMapper;
import com.lipaluma.field.mapper.mapping.FieldMappingInfo;
import com.lipaluma.field.validator.strategies.ValidationStrategyEnum;
import com.lipaluma.io.SourceReader;
import com.lipaluma.parser.builder.FromParserFieldMapperBuilder;

/**
 * RowMapper like {@link ColumnDataParser} maps each line of a CSV or Excel file to an Object where the target class is defined by the user.
 * <p>
 * The difference is that RowMapper can map manually line per line. The user manage himself the construction of the list of objects.
 * 
 * @author Mario
 *
 * @param <TARGET> the target entity defined by the user
 */
public abstract class AbstractDataParserIterable<TARGET> extends AbstractDataParser<TARGET> implements Iterable<TARGET>, Iterator<TARGET>, Closeable {
	private static final Logger LOG = LoggerFactory.getLogger(AbstractDataParserIterable.class);
	protected SourceReader sourceReader;
	protected ValidationStrategyEnum validationStrategy;

	protected AbstractDataParserIterable(Builder<? extends AbstractDataParserIterable<TARGET>, TARGET> builder) {
		super(builder);
		this.sourceReader = builder.sourceReader;
		this.validationStrategy = builder.validationStrategy;
	}

	@Override
	public boolean hasNext() {
		return sourceReader.hasNext();
	}
	
	/**
	 * Process the next row of the entry file for being mapped on the target entity.
	 * 
	 * @return the Object mapped
	 */
	@Override
	public TARGET next() {
		if(sourceReader == null)
			throw new CsvParserException("The input source (SourceReader) is missing.");
		try {
			if(sourceReader.hasNext())
				return unmarshal(sourceReader.next());
		} catch (CsvParserValidationException ex) {
			if(validationStrategy.equals(ValidationStrategyEnum.FILTER_VALIDATION_ERRORS)) {
				LOG.warn("Validation Error [object number "+sourceReader.getCurrentLineNumber()+"]"+ex.getMessage());
				LOG.warn("The line will be filtered.");
				return next();
			}
		}
		return null;
	}

	/**
	 * close the input entry
	 */
	@Override
	public void close() {
		sourceReader.close();
	}
	
	@Override
	public void remove() {}
	
	@Override
	public Iterator<TARGET> iterator() {
		return this;
	}
	
	public void setSourceReader(SourceReader sourceReader) {
		this.sourceReader = sourceReader;
	}

	public void setValidationStrategy(ValidationStrategyEnum validationStrategy) {
		this.validationStrategy = validationStrategy;
	}
	
	public static abstract class Builder<PARSER extends DataParser<TARGET>, TARGET> extends AbstractDataParser.Builder<PARSER, TARGET> {
		private ValidationStrategyEnum validationStrategy; 
		private SourceReader sourceReader;
		
		protected Builder(Class<TARGET> targetClass, Context context) {
			super(targetClass, context);
		}
		
		public Builder<PARSER, TARGET> withSourceReader(SourceReader sourceReader) {
			this.sourceReader = sourceReader;
			return this;
		}

		@Override
		public Builder<PARSER, TARGET> withFieldMappers(List<FieldMapper> mappers) {
			super.withFieldMappers(mappers);
			return this;
		}
		
		public Builder<PARSER, TARGET> withValidationStrategy(ValidationStrategyEnum strategy) {
			this.validationStrategy = strategy;
			return this;
		}
	}
}
